package controllers

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"

	"github.com/golang/glog"
)

type ValueDescriptor struct {
	Id            string      `json:"id,omitempty"`
	Created       int64       `json:"created,omitempty"`
	Description   string      `json:"description,omitempty"`
	Modified      int64       `json:"modified,omitempty"`
	Origin        int64       `json:"origin,omitempty"`
	Name          string      `json:"name,omitempty"`
	Min           interface{} `json:"min,omitempty"`
	Max           interface{} `json:"max,omitempty"`
	DefaultValue  interface{} `json:"defaultValue,omitempty"`
	Type          string      `json:"type,omitempty"`
	UomLabel      string      `json:"uomLabel,omitempty"`
	Formatting    string      `json:"formatting,omitempty"`
	Labels        []string    `json:"labels,omitempty"`
	MediaType     string      `json:"mediaType,omitempty"`
	FloatEncoding string      `json:"floatEncoding,omitempty"`
	isValidated   bool        // internal member used for validation check
}

type Reading struct {
	Id            string `json:"id,omitempty" codec:"id,omitempty"`
	Pushed        int64  `json:"pushed,omitempty" codec:"pushed,omitempty"`   // When the data was pushed out of EdgeX (0 - not pushed yet)
	Created       int64  `json:"created,omitempty" codec:"created,omitempty"` // When the reading was created
	Origin        int64  `json:"origin,omitempty" codec:"origin,omitempty"`
	Modified      int64  `json:"modified,omitempty" codec:"modified,omitempty"`
	Device        string `json:"device,omitempty" codec:"device,omitempty"`
	Name          string `json:"name,omitempty" codec:"name,omitempty"`
	Value         string `json:"value,omitempty" codec:"value,omitempty"` // Device sensor data value
	ValueType     string `json:"valueType,omitempty" codec:"valueType,omitempty"`
	FloatEncoding string `json:"floatEncoding,omitempty" codec:"floatEncoding,omitempty"`
	// BinaryValue binary data payload. This information is not persisted in the Database and is expected to be empty
	// when retrieving a Reading for the ValueType of Binary.
	BinaryValue []byte `json:"binaryValue,omitempty" codec:"binaryValue,omitempty"`
	MediaType   string `json:"mediaType,omitempty" codec:"mediaType,omitempty"`
	isValidated bool   // internal member used for validation check
}

func getDeviceValueDescriptor(deviceName string, gateway string) ([]ValueDescriptor, error) {
	url := "http://" + gateway + ":48080/api/v1/valuedescriptor/devicename/" + deviceName
	resp, err := http.Get(url)
	if err != nil {
		glog.Errorf("Get device value descriptor error: %v", err)
		return nil, err
	}
	defer resp.Body.Close()
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("read valueDescriptor from response error: %v", err)
		return nil, err
	}
	var vd []ValueDescriptor
	err = json.Unmarshal(bytes, &vd)
	if err != nil {
		glog.Errorf("Unmarshal valueDescriptor error: %v", err)
		return nil, err
	}
	return vd, nil
}

type point struct {
	T     []string
	Value []interface{}
}

func getDeviceValue(timeStamp int64, gateWay string, deviceName, valueName string) (point, error) {
	var r = make([]Reading, 0)
	var p point

	end := time.Now().UnixNano() / 1e6
	start := end - timeStamp*3600*1000
	limit := strconv.FormatInt(timeStamp*500, 10)
	url := "http://" + gateWay + ":48080/api/v1/reading/name/" + valueName + "/device/" + deviceName + "/" + limit
	resp, err := http.Get(url)
	if err != nil {
		glog.Errorf("Get device readings error: %v", err)
		return p, err
	}
	defer resp.Body.Close()
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Errorf("read device readings from response error: %v", err)
		return p, err
	}

	err = json.Unmarshal(bytes, &r)
	if err != nil {
		glog.Errorf("Unmarshal device readings error: %v", err)
		return p, err
	}
	var cstZone = time.FixedZone("CST", 8*3600)
	readingLength := len(r)
	p.T = make([]string, readingLength)
	p.Value = make([]interface{}, readingLength)
	for i, reading := range r {
		if reading.Created >= start {
			stamp := reading.Created / 1e3
			format := time.Unix(int64(stamp), 0).In(cstZone).Format("2006-01-02 15:04:05")
			f, _ := strconv.ParseFloat(reading.Value, 10)
			p.T[readingLength-i-1] = format
			p.Value[readingLength-i-1] = f
		}
	}
	return p, nil
}

func getDeviceValuePoint(deviceName, gateWay string, timeStamp int64) (map[string]point, error) {
	vd, err := getDeviceValueDescriptor(deviceName, gateWay)
	if err != nil {
		glog.Errorf("failed get device value descriptor, %v", err)
		return nil, err
	}
	m := make(map[string]point)
	for _, valueDescriptor := range vd {
		m[valueDescriptor.Name], err = getDeviceValue(timeStamp, gateWay, deviceName, valueDescriptor.Name)
		if err != nil {
			glog.Errorf("failed get device value, timeStamp: %v, gateWay: %v, deviceName: %v, valueDescriptor.Name: %v, %v", timeStamp, gateWay, deviceName, valueDescriptor.Name, err)
			return m, err
		}
	}
	return m, nil
}

type requestBody struct {
	DeviceName string `json:"deviceName"`
	GateWay    string `json:"gateway"`
	TimeStamp  string `json:"timeStamp"`
}

func GetDeviceValueHandler(w http.ResponseWriter, req *http.Request) {
	defer func() { _ = req.Body.Close() }()

	var body requestBody
	err := json.NewDecoder(req.Body).Decode(&body)
	if err != nil {
		glog.Errorf("failed unmarshal input body, %v", err)
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(fmt.Sprintf("%v", err)))
		return
	}
	stamp, err := strconv.Atoi(body.TimeStamp)
	if err != nil {
		glog.Errorf("failed transfer timestamp, %v", err)
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(fmt.Sprintf("%v", err)))
		return
	}

	valuePoint, err := getDeviceValuePoint(body.DeviceName, body.GateWay, int64(stamp))
	if err != nil {
		glog.Errorf("failed get device value point, %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte(fmt.Sprintf("failed get device value point, %v", err)))
		return
	}
	bytes, err := json.Marshal(valuePoint)
	if err != nil {
		glog.Errorf("device value marshal error: %v", err)
		w.WriteHeader(http.StatusBadGateway)
		_, _ = w.Write([]byte(fmt.Sprintf("device value marshal error: %v", err)))
		return
	}
	_, _ = w.Write(bytes)
}
